import functools
import time

import logzero
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import (
    QCheckBox,
    QDoubleSpinBox,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QSpinBox,
    QWidget,
    QComboBox,
)

from helpers.mengy_bytes_helper import bytes2Int, getChineseStringLength
import mengy_spac800
import mengy_spac800_bt
from qt_custom_widget.qt_helper import addTooltipInSpinBox, dealGroubBpxDoubleClick
from qt_custom_widget.qt_ScrollMessageBox import ScrollMessageBox
import stackprinter

logger = logzero.setup_logger("tabs.4.prt")


class Tab4_Prt_Para_1(QWidget):
    def __init__(self, serRecvDir, sendQueuePack, logger=logger):
        super().__init__()
        self.logger = logger
        self.initUI()
        self.refreshTime = time.time()
        self.serRecvDir = serRecvDir
        self.sendQueuePack = sendQueuePack
        self.logger.info("this is tab4 prt para part 1/2 logger")
        self.setObjectName("tab4")
        layout = QGridLayout(self)
        layout.addWidget(self.prtParaGroupBox_1, 0, 0)  # 保护参数 1

    def initUI(self):
        """初始化显示构件"""
        self.createPrtParaGroupBox_1()

    def createPrtParaSubGroupBox(self, idx, layout):
        """创建保护参数 子布局"""
        self.ptrParaDoData = [0 for i in range(len(mengy_spac800.DO_PRT_TYPE_NAME))]
        conf = mengy_spac800.PTR_SUB_CONFS[idx]
        subGB = QGroupBox(conf.name)
        subGB.setCheckable(True)
        subLayout = QGridLayout()
        if conf.doSelectIndex is not None:
            subGB.setToolTip("双击左键反选\n双击右键全选\n双击中键全不选")
            subGB.setToolTipDuration(3000)
            subGB.mouseDoubleClickEvent = functools.partial(
                dealGroubBpxDoubleClick, self.prtDOSelectCheckBoxs[conf.doSelectIndex]
            )
            subLayout.addLayout(self.prtDOSelectGridLayouts[conf.doSelectIndex], 0, 0, 1, 2)
        self.prtWdgts[conf.index] = subGB
        for i, r in enumerate(conf.range):
            subLayout.addWidget(QLabel(conf.itemNames[i]), i + 1, 0)
            if isinstance(r[0], int):
                pWdgt = QSpinBox()
                pWdgt.setSuffix(conf.unitNames[i])
                addTooltipInSpinBox(pWdgt, r)
                pWdgt.setToolTipDuration(3000)
            elif isinstance(r[0], float):
                pWdgt = QDoubleSpinBox()
                pWdgt.setSuffix(conf.unitNames[i])
                pWdgt.setDecimals(2)
                addTooltipInSpinBox(pWdgt, r)
                pWdgt.setToolTipDuration(3000)
            self.prtWdgts[conf.index + i + 1] = pWdgt
            subLayout.addWidget(pWdgt, i + 1, 1)
        subGB.setLayout(subLayout)
        layout.addWidget(subGB, *conf.groupPlace)

    def createPrtParaGroupBox_1(self):
        """创建保护参数布局"""
        self.prtParaOverRangeFlag = False
        self.prtParaGroupBox_1 = QGroupBox("保护定值 (电流 电压)")
        layout = QGridLayout(self.prtParaGroupBox_1)
        self.prtWdgts = [None for i in range(47)]

        self.prtDOSelectCheckBoxs = []
        self.prtDOSelectGridLayouts = []
        for _ in range(len(mengy_spac800.DO_PRT_TYPE_NAME)):
            checkBoxs = []
            gLayout = QGridLayout()
            for di in range(len(mengy_spac800.DO_NAME)):
                qcb = QCheckBox(str(di + 1))
                qcb.setToolTip("1-8代表开出1-8\n9为保护灯\n10为告警灯")
                qcb.setToolTipDuration(3000)
                gLayout.addWidget(qcb, di // 5, di % 5)
                checkBoxs.append(qcb)
            self.prtDOSelectGridLayouts.append(gLayout)
            self.prtDOSelectCheckBoxs.append(checkBoxs)

        for i in range(15):
            self.createPrtParaSubGroupBox(i, layout)

        subGB = QGroupBox("读写控制")
        subLayout = QHBoxLayout()
        self.prtParaGroupBoxReadButton_1 = QPushButton("读取")
        self.prtParaGroupBoxWriteButton_1 = QPushButton("写入")
        self.prtParaGroupBoxReadButton_1.clicked.connect(self.dealPrtParaButton)
        self.prtParaGroupBoxWriteButton_1.clicked.connect(self.dealPrtParaButton)
        subLayout.addWidget(self.prtParaGroupBoxReadButton_1)
        subLayout.addWidget(self.prtParaGroupBoxWriteButton_1)
        subGB.setLayout(subLayout)
        layout.addWidget(subGB, 8, 4, 1, 1)

    def doParaGetPrtData(self):
        """获取开出参数中 保护对应的开出配置"""
        if any((key not in self.serRecvDir.keys() for key in range(0x040A, 0x0426 + 1))):
            return
        for i in range(len(mengy_spac800.DO_PRT_TYPE_NAME)):
            data = bytes2Int(self.serRecvDir.get(0x040A + i))
            self.ptrParaDoData[i] = data
            # self.logger.info(f"{i+1:>2d} --> 0b{data:010b}")

    def prtParaGroupBoxRead(self):
        """"刷新保护参数显示"""
        raw = tuple(self.serRecvDir.get(i) for i in range(0x0800, 0x082E + 1))
        if any((i is None for i in raw)):
            return
        values = tuple(bytes2Int(i) for i in raw)
        errMsgTextList = []
        for ind, wdgt in enumerate(self.prtWdgts):
            value = values[ind]
            if isinstance(wdgt, QGroupBox):
                if value > 1:
                    wdgt.setStyleSheet("color: red;")
                    prtName = wdgt.title() + "投退定值"
                    prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                    detailText = "{} > 0".format(value)
                    subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, 0, 1)
                    errMsgTextList.append(subUnitErrMsgText)
                else:
                    wdgt.setStyleSheet("")
                    if value > 0:
                        wdgt.setChecked(True)
                    else:
                        wdgt.setChecked(False)
            elif isinstance(wdgt, QSpinBox) or isinstance(wdgt, QDoubleSpinBox):
                if isinstance(wdgt, QDoubleSpinBox):
                    value = value / (10 ** wdgt.decimals())
                maximum = wdgt.maximum()
                minimum = wdgt.minimum()
                if value > maximum or value < minimum:
                    wdgt.setStyleSheet("color: red;")
                    prtName = mengy_spac800.getPrtAreaName(ind)
                    prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                    detailText = "{} {}".format(
                        value, "> {}".format(maximum) if value > maximum else "< {}".format(minimum)
                    )
                    subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, minimum, maximum)
                    errMsgTextList.append(subUnitErrMsgText)
                else:
                    wdgt.setStyleSheet("")
                wdgt.setValue(value)
        if len(errMsgTextList) > 0 and not self.prtParaOverRangeFlag:
            self.prtParaOverRangeFlag = True
            messageBox = ScrollMessageBox(msgList=errMsgTextList, parent=self)
            messageBox.setWindowTitle("保护参数超范围")
            messageBox.exec_()
            self.prtParaOverRangeFlag = False

    def prtParaGroupBoxWrite(self):
        """写入保护参数"""
        data = []
        for wdgt in self.prtWdgts:
            if isinstance(wdgt, QSpinBox):
                data.append(wdgt.value())
            elif isinstance(wdgt, QDoubleSpinBox):
                data.append(int(round(wdgt.value() * (10 ** wdgt.decimals()))))
            elif isinstance(wdgt, QGroupBox):
                wdgt.setStyleSheet("color:#ff0000;")
                if wdgt.isChecked():
                    data.append(1)
                else:
                    data.append(0)
        self.logger.info("in prt data is {}".format(data))
        self.sendQueuePack(funCode=0x10, start=0x0800, data=data)

    def PrtDOSelectGroupBoxRead(self):
        """保护开出读取"""
        self.doParaGetPrtData()
        for idx, value in enumerate(self.ptrParaDoData):
            # self.logger.info(f'index {idx} value {value:010b}')
            for i in range(len(mengy_spac800.DO_NAME)):
                if value & (1 << i):
                    self.prtDOSelectCheckBoxs[idx][i].setChecked(True)
                else:
                    self.prtDOSelectCheckBoxs[idx][i].setChecked(False)

    def PrtDOSelectGroupBoxWrite(self):
        """保护开出写入"""
        for idx, cs in enumerate(self.prtDOSelectCheckBoxs):
            ptrDoSelect = 0
            for i, c in enumerate(cs):
                if c.isChecked():
                    ptrDoSelect += 1 << i
            self.ptrParaDoData[idx] = ptrDoSelect
        data = self.ptrParaDoData
        self.sendQueuePack(funCode=0x10, start=0x040A, data=data)

    def dealPrtParaButton(self):
        """保护参数按钮处理"""
        sender = self.sender()
        if sender is self.prtParaGroupBoxReadButton_1:
            self.prtParaGroupBoxRead()
            self.PrtDOSelectGroupBoxRead()
        elif sender is self.prtParaGroupBoxWriteButton_1:
            self.prtParaGroupBoxWrite()
            self.PrtDOSelectGroupBoxWrite()
            QTimer.singleShot(1500, self.prtParaGroupBoxRead)
            QTimer.singleShot(1500, self.PrtDOSelectGroupBoxRead)

    def updateBySerialRecvBuff(self, changeAddrList):
        """数据缓存发生变化时需要刷新部分"""
        if any(i in range(0x0800, 0x082E + 1) for i in changeAddrList):
            self.prtParaGroupBoxRead()
            self.refreshTime = time.time()
        if any(i in range(0x040A, 0x0426 + 1) for i in changeAddrList):
            self.PrtDOSelectGroupBoxRead()
            self.refreshTime = time.time()

    def updateByTimer(self, timeout=1):
        if time.time() - self.refreshTime > timeout:
            self.prtParaGroupBoxRead()
            self.PrtDOSelectGroupBoxRead()
            self.refreshTime = time.time()

    def updateUnconditional(self):
        """无条件刷新界面显示部分"""
        pass

    def updateWhole(self):
        """更新全体显示构件"""
        self.prtParaGroupBoxRead()
        self.PrtDOSelectGroupBoxRead()
        self.refreshTime = time.time()


class Tab4_Prt_Para_BT(Tab4_Prt_Para_1):
    def createPrtParaGroupBoxs(self, layout):
        self.groupBoxsInfo = []
        for conf in mengy_spac800_bt.PTR_GROUP_CONFS:
            subGB = QGroupBox(conf.name)
            subLayout = QGridLayout(subGB)
            if conf.isCheckable:
                subGB.setCheckable(True)
            self.groupBoxsInfo.append((subGB, subLayout, conf))
            layout.addWidget(subGB, *conf.groupPlace)
        self.createPrtParaSubGroupBoxs()

    def createPrtParaSubGroupBoxs(self):
        """创建保护参数 子布局"""
        self.paraWdgtsInfo = []
        for conf in mengy_spac800_bt.PTR_SUB_CONFS:
            groupIndex, groupSubIndex = conf.groupIndex
            subLayout = self.groupBoxsInfo[groupIndex][1]
            r = conf.range
            if isinstance(r[0], int):
                if r[0] == 0 and r[1] == 1:
                    pWdgt = QComboBox()
                    pWdgt.addItems(("退出", "投入"))
                else:
                    pWdgt = QSpinBox()
                    pWdgt.setSuffix(conf.unitName)
                    addTooltipInSpinBox(pWdgt, r)
                    pWdgt.setToolTipDuration(3000)
            elif isinstance(r[0], float):
                pWdgt = QDoubleSpinBox()
                pWdgt.setSuffix(conf.unitName)
                pWdgt.setDecimals(2)
                addTooltipInSpinBox(pWdgt, r)
                pWdgt.setToolTipDuration(3000)
            subLayout.addWidget(QLabel(conf.itemName), groupSubIndex, 0)
            subLayout.addWidget(pWdgt, groupSubIndex, 1)
            self.paraWdgtsInfo.append((pWdgt, conf))

    def createPrtParaGroupBox_1(self):
        """创建保护参数布局"""
        self.prtParaOverRangeFlag = False
        self.prtParaGroupBox_1 = QGroupBox("保护定值")
        layout = QGridLayout(self.prtParaGroupBox_1)

        self.createPrtParaGroupBoxs(layout)

        subGB = QGroupBox("读写控制")
        subLayout = QHBoxLayout()
        self.prtParaGroupBoxReadButton_1 = QPushButton("读取")
        self.prtParaGroupBoxWriteButton_1 = QPushButton("写入")
        self.prtParaGroupBoxReadButton_1.clicked.connect(self.dealPrtParaButton)
        self.prtParaGroupBoxWriteButton_1.clicked.connect(self.dealPrtParaButton)
        subLayout.addWidget(self.prtParaGroupBoxReadButton_1)
        subLayout.addWidget(self.prtParaGroupBoxWriteButton_1)
        subGB.setLayout(subLayout)
        layout.addWidget(subGB, 6, 4, 2, 1)

    def prtParaGroupBoxRead(self):
        """"刷新保护参数显示"""
        raw = tuple(self.serRecvDir.get(i) for i in range(0x0800, 0x083A + 1))
        if any((i is None for i in raw)):
            return
        values = tuple(bytes2Int(i) for i in raw)
        self.logger.info("prt values is {} | {}".format(len(values), values))
        errMsgTextList = []
        try:
            for wdgt, _, conf in self.groupBoxsInfo:
                value = values[conf.offset]
                if conf.isCheckable:
                    if value > 1:
                        wdgt.setStyleSheet("color: red;")
                        prtName = wdgt.title() + "投退定值"
                        prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                        detailText = "{} > 1".format(value)
                        subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, 0, 1)
                        errMsgTextList.append(subUnitErrMsgText)
                    else:
                        wdgt.setStyleSheet("")
                        if value > 0:
                            wdgt.setChecked(True)
                        else:
                            wdgt.setChecked(False)

            for wdgt, conf in self.paraWdgtsInfo:
                value = values[conf.offset]
                if isinstance(wdgt, QSpinBox) or isinstance(wdgt, QDoubleSpinBox):
                    if isinstance(wdgt, QDoubleSpinBox):
                        value = value / (10 ** wdgt.decimals())
                    maximum = wdgt.maximum()
                    minimum = wdgt.minimum()
                    if value > maximum or value < minimum:
                        wdgt.setStyleSheet("color: red;")
                        prtName = mengy_spac800_bt.getPrtAreaName(conf.offset)
                        prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                        detailText = "{} {}".format(
                            value, "> {}".format(maximum) if value > maximum else "< {}".format(minimum)
                        )
                        subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, minimum, maximum)
                        errMsgTextList.append(subUnitErrMsgText)
                    else:
                        wdgt.setStyleSheet("")
                    wdgt.setValue(value)
                elif isinstance(wdgt, QComboBox):
                    maximum = wdgt.count()
                    if value < maximum:
                        wdgt.setCurrentIndex(value)
                    else:
                        prtName = mengy_spac800_bt.getPrtAreaName(conf.offset)
                        prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                        detailText = "{} > {}".format(value, maximum)
                        subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, 0, maximum)
                        errMsgTextList.append(subUnitErrMsgText)
        except Exception:
            self.logger.error(stackprinter.format())

        if len(errMsgTextList) > 0 and not self.prtParaOverRangeFlag:
            self.prtParaOverRangeFlag = True
            messageBox = ScrollMessageBox(msgList=errMsgTextList, parent=self)
            messageBox.setWindowTitle("保护参数超范围")
            messageBox.exec_()
            self.prtParaOverRangeFlag = False

    def prtParaGroupBoxWrite(self):
        """写入保护参数"""
        raw = tuple(self.serRecvDir.get(i) for i in range(0x0800, 0x083A + 1))
        if any((i is None for i in raw)):
            data = [0] * (0x083A + 1 - 0x0800)
        else:
            data = [bytes2Int(i) for i in raw]
        for wdgt, _, conf in self.groupBoxsInfo:
            if conf.isCheckable:
                wdgt.setStyleSheet("color:#ff0000;")
                if wdgt.isChecked():
                    data[conf.offset] = 1
                else:
                    data[conf.offset] = 0

        for pWdgt, conf in self.paraWdgtsInfo:
            if isinstance(pWdgt, QSpinBox):
                data[conf.offset] = pWdgt.value()
            elif isinstance(pWdgt, QDoubleSpinBox):
                data[conf.offset] = int(round(pWdgt.value() * (10 ** pWdgt.decimals())))
            elif isinstance(pWdgt, QComboBox):
                data[conf.offset] = pWdgt.currentIndex()
        self.logger.info("in prt data is {}".format(data))
        self.sendQueuePack(funCode=0x10, start=0x0800, data=data)

    def dealPrtParaButton(self):
        """保护参数按钮处理"""
        sender = self.sender()
        if sender is self.prtParaGroupBoxReadButton_1:
            self.prtParaGroupBoxRead()
        elif sender is self.prtParaGroupBoxWriteButton_1:
            self.prtParaGroupBoxWrite()
            QTimer.singleShot(1500, self.prtParaGroupBoxRead)

    def updateBySerialRecvBuff(self, changeAddrList):
        """数据缓存发生变化时需要刷新部分"""
        if any(i in range(0x0800, 0x083A + 1) for i in changeAddrList):
            self.prtParaGroupBoxRead()
            self.refreshTime = time.time()

    def updateByTimer(self, timeout=1):
        if time.time() - self.refreshTime > timeout:
            self.prtParaGroupBoxRead()
            self.refreshTime = time.time()

    def updateUnconditional(self):
        """无条件刷新界面显示部分"""
        pass

    def updateWhole(self):
        """更新全体显示构件"""
        self.prtParaGroupBoxRead()
        self.refreshTime = time.time()
